#include "elipse_renderer.h"
#include "engine/utils/file_utils.h"
#include "GL/glew.h"

namespace kanon
{
    ElipseBatch::~ElipseBatch()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteBuffers(1, &vbo);
        glDeleteBuffers(1, &ibo);
    }

    ElipseBatch::ElipseBatch(uint32_t max_elipses)
    {
        max_vertices = max_elipses * 4;
        max_indices = max_elipses * 6;
        num_vertices = 0;
        num_indices = 0;

        vertex_base = nullptr;
        index_base = nullptr;

        // create vao and vbo
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, max_vertices * sizeof(ElipseVertex), nullptr, GL_DYNAMIC_DRAW);

        // set vertex attributes
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ElipseVertex), (void*)offsetof(ElipseVertex, pos));
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(ElipseVertex), (void*)offsetof(ElipseVertex, center));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(ElipseVertex), (void*)offsetof(ElipseVertex, radius));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(ElipseVertex), (void*)offsetof(ElipseVertex, color));
        glEnableVertexAttribArray(3);

        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_indices * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);
    }

    void ElipseBatch::Begin()
    {
        // map buffer
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        vertex_base = (ElipseVertex*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        index_base = (uint32_t*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
        num_vertices = 0;
        num_indices = 0;
    }

    void ElipseBatch::End()
    {
        glUnmapBuffer(GL_ARRAY_BUFFER);
        vertex_base = nullptr;
        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
        index_base = nullptr;
    }

    bool ElipseBatch::Submit(const Elipse& elipse)
    {
        uint32_t particle_indices = 6;
        uint32_t particle_vertices = 4;
        if (num_indices + particle_indices > max_indices)
            return false;
        if (num_vertices + particle_vertices > max_vertices)
            return false;

        // copy vertex data
        ElipseVertex* data = vertex_base + num_vertices;
        glm::vec2 tl = elipse.center - glm::vec2(elipse.radius.x, elipse.radius.y);
        glm::vec2 br = elipse.center + glm::vec2(elipse.radius.x, elipse.radius.y);

        data[0].pos = { tl.x, tl.y };
        data[0].center = elipse.center;
        data[0].radius = elipse.radius;
        data[0].color = elipse.color;

        data[1].pos = { tl.x, br.y };
        data[1].center = elipse.center;
        data[1].radius = elipse.radius;
        data[1].color = elipse.color;

        data[2].pos = { br.x, br.y };
        data[2].center = elipse.center;
        data[2].radius = elipse.radius;
        data[2].color = elipse.color;

        data[3].pos = { br.x, tl.y };
        data[3].center = elipse.center;
        data[3].radius = elipse.radius;
        data[3].color = elipse.color;

        // copy index data
        uint32_t* index_data = index_base + num_indices;
        index_data[0] = num_vertices + 0;
        index_data[1] = num_vertices + 1;
        index_data[2] = num_vertices + 2;
        index_data[3] = num_vertices + 0;
        index_data[4] = num_vertices + 2;
        index_data[5] = num_vertices + 3;

        num_vertices += 4;
        num_indices += 6;
        return true;
    }

    ElipseRenderer::ElipseRenderer()
    {
        std::string vertex_shader_path = FileUtils::getShaderPath("elipse_renderer.vs");
        std::string fragment_shader_path = FileUtils::getShaderPath("elipse_renderer.fs");
        shader = Shader::FromShaderFiles(vertex_shader_path, fragment_shader_path);
        batch = std::make_shared<ElipseBatch>(50000);
    }

    void ElipseRenderer::SetCamera(const std::shared_ptr<Camera2d>& cam)
    {
        this->camera = cam;
    }

    void ElipseRenderer::Begin()
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        batch->Begin();
    }

    bool ElipseRenderer::Render(const Elipse& elipse)
    {
        bool success = batch->Submit(elipse);
        if (!success)
        {
            this->Flush();
            this->Begin();
            batch->Submit(elipse);
        }
        return true;
    }

    void ElipseRenderer::Render(const std::vector<Elipse>& elipse)
    {
        for (auto& p : elipse)
        {
            this->Render(p);
        }
    }

    void ElipseRenderer::End()
    {
        this->Flush();
    }

    void ElipseRenderer::Flush()
    {
        batch->End();
        shader->Bind();

        if (this->camera)
        {
            glm::mat4 view = this->camera->GetViewMatrix();
            glm::mat4 proj = this->camera->GetProjectionMatrix();
            shader->set_matrix("vp", proj * view);
        }

        glDrawElements(GL_TRIANGLES, batch->num_indices, GL_UNSIGNED_INT, nullptr);
        shader->Unbind();
    }
}
